﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Reflection;
using System.ComponentModel;
using System.IO.Compression;

namespace Luna
{
	public static class ObjectExtension
	{
		public static void writeObject<T>(this T obj, Stream s) {
			BinaryFormatter fmt = new BinaryFormatter();
			fmt.Serialize(s, obj);
		}

		public static void writeCompressedObject<T>(this T obj, Stream s) {
			using (DeflateStream ds = new DeflateStream(s, CompressionMode.Compress)) {
				obj.writeObject(ds);
				ds.Close();
			}
		}

		public static T readObject<T>(this Stream s) {
			BinaryFormatter fmt = new BinaryFormatter();
			return (T)fmt.Deserialize(s);
		}

		public static T readCompressedObject<T>(this Stream s) {
			T result;
			using (DeflateStream ds = new DeflateStream(s, CompressionMode.Decompress)) {
				result = ds.readObject<T>();
			}
			return result;
		}

		public static void writeObject<T>(this T obj, string filename) {
			using (FileStream f = new FileStream(filename, FileMode.Create))
				writeObject(obj, f);
		}

		public static void writeCompressedObject<T>(this T obj, string filename) {
			using (FileStream f = new FileStream(filename, FileMode.Create))
				writeCompressedObject(obj, f);
		}

		public static T readObject<T>(this string filename)  {
			using (FileStream f = new FileStream(filename, FileMode.Open))
				return readObject<T>(f);
		}

		public static T readCompressedObject<T>(this string filename) {
			using (FileStream f = new FileStream(filename, FileMode.Open))
				return readCompressedObject<T>(f);
		}

		public static T getPropertyValue<T>(this object obj, string propName, object[] index) {
			PropertyInfo prop = obj.GetType().GetProperty(propName);
			if (prop != null)
				return (T) prop.GetValue(obj, index);
			return default(T);
		}

		public static void setPropertyValue<T>(this object obj, string propName, T value, object[] index) {
			PropertyInfo prop = obj.GetType().GetProperty(propName);
			if (prop != null)
				prop.SetValue(obj, value, index);
		}

		public static object createInstance(this Type type, Type[] para, object[] args) {
			ConstructorInfo ctor = type.GetConstructor(para);
			if (ctor != null)
				return ctor.Invoke(args);
			return null;
		}

		/// <summary>
		/// Return <paramref name="defaultValue"/> if <paramref name="obj"/> equals to default(<paramref name="obj"/>).
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="obj"></param>
		/// <param name="defaultValue">New default value</param>
		/// <returns></returns>
		public static T getDefaultValue<T>(this T obj, T defaultValue) {
			return object.Equals(obj, default(T)) ? defaultValue : obj;
		}

		public static MemberInfo getMember(this Type type, string memberName) {
			return type.GetMember(memberName, BindingFlags.Instance
						| BindingFlags.Public | BindingFlags.Static
						| BindingFlags.NonPublic).FirstOrDefault();
		}

		public static object dynamicInvoke(this object obj, string methodName, object[] args) {
			MethodInfo med = obj.getMember<MethodInfo>(methodName, true);
			return med.Invoke(obj, args);
		}

		public static MemberInfo getMember(this object obj, string memberName) {
			for (int periodIdx = 0; obj != null && memberName.isNotNullAndEmpty() && (periodIdx = memberName.IndexOf('.')) >= 0; ) {
				MemberInfo propInfo = getMember(obj.GetType(), memberName.Substring(0, periodIdx));

				if (propInfo == null) throw new MissingMemberException(string.Format("{0}.{1}", obj.GetType().Name, memberName.Substring(0, periodIdx)));

				if (propInfo is FieldInfo) obj = (propInfo as FieldInfo).GetValue(obj);
				else obj = (propInfo as PropertyInfo).GetValue(obj, null);

				memberName = memberName.Substring(periodIdx + 1);
			}
			if (null == obj) return null;
			MemberInfo result = getMember(obj.GetType(), memberName);

			return result;
		}

		public static T getMember<T>(this object obj, string memberName, bool throwException) where T : MemberInfo {
			try {
				return (T)getMember(obj, memberName);
			}
			catch (Exception) {
				if (throwException) throw;
				return null;
			}
		}

		public static object convertTo(this string s, Type target) {
			TypeConverter convert = TypeDescriptor.GetConverter(target);
			return convert != null ? convert.ConvertFromString(s): Convert.ChangeType(s, target);
		}

		public static T convertTo<T>(this string s) {
			return (T)convertTo(s, typeof(T));
		}

		public static T convertTo<T>(this string s, T defaultValue) {
			try {
				return (T)convertTo(s, typeof(T));
			}
			catch(Exception ex) {
				return defaultValue;
			}
		}

		public static IAppContext extractAppContext(this object obj) {
			IAppContext context = null;
			if (obj != null)
				context = obj.getPropertyValue<IAppContext>("Context", null);
			return context;
		}
	}
}
